﻿using System;
using System.ComponentModel;
using ActionLogic;
using BehaviorModel.Block.RandomGenerator;
using BehaviorModel.Statistic;
using Model;
using Model.Block;
using BehaviorModel.XmlSerializer;

namespace BehaviorModel.Block.ServiceChannel
{
    /// <summary>
    /// Service device type.
    /// </summary>
    public enum ServiceChannelType
    {
        Loss,
        Queue,
        Waiting
    }

    /// <summary>
    /// Describes implementation of behavior model of service channel in QSM.
    /// </summary>
    public class ServiceChannel : IServiceChannel
    {
        #region Fields

        private Request _servedRequest;

        private ServiceChannelType _channelType;

        private RandomGeneratorType _generatorType;

        private IReceiverBlock _receiverBlock;

        private IRequestBuffer _innerBuffer;


        #endregion

        #region Properties

        #region IBlock

        public Guid Id { get; private set; }

        public BlockType Type { get { return BlockType.ServiceChannel; } }

        public IBlockStatistic Statistic { get; private set; }

        #endregion

        #region IExecutable

        public event Action<PriorityAction> OnActionExecuted;

        #endregion

        #region IServiceChannel

        public ServiceChannelStorage Storage { get; private set; }

        public bool IsBusy { get; set; }

        public ServiceChannelType ChannelType
        {
            get { return _channelType; }
            set
            {
                if (_channelType == value)
                {
                    return;
                }

                _channelType = value;
                SetServiceChannelType();
                RaisePropertyChanged("ChannelType");
            }
        }

        public int MaxQueueLength
        {
            get { return _innerBuffer.MaxCapacity; }
            set { _innerBuffer.MaxCapacity = value; }
        }

        public int QueueLength { get { return _innerBuffer.Count; } }

        public ChoosingDiscipline Discipline
        {
            get { return _innerBuffer.Discipline; }
            set { _innerBuffer.Discipline = value; }
        }

        public IRandomGenerator ServiceGenerator { get; set; }

        public RandomGeneratorType GeneratorType
        {
            get { return _generatorType; }
            set
            {
                if (_generatorType == value)
                {
                    return;
                }

                _generatorType = value;
                ServiceGenerator = RandomGeneratorFactory.GetInstance().Create(_generatorType);
                RaisePropertyChanged("GeneratorType");
            }
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #endregion

        #region Constructor

        public ServiceChannel()
        {
            Id = Guid.NewGuid();
            GeneratorType = RandomGeneratorType.Poisson;
            ChannelType = ServiceChannelType.Loss;
            IsBusy = false;

            Storage = new ServiceChannelStorage();
            Statistic = new ServiceChannelStatistic(this);
            createInnerBufferWithRequestHandlers();
        }

        private void createInnerBufferWithRequestHandlers()
        {
            _innerBuffer = new RequestBuffer();
            _innerBuffer.OnRequestRejected += RejectRequest;
            _innerBuffer.OnRequestAdded += Service;
            _innerBuffer.Discipline = ChoosingDiscipline.Fifo;
        }

        #endregion

        #region Methods

        private void SetServiceChannelType()
        {
            switch (_channelType)
            {
                case ServiceChannelType.Waiting:
                    MaxQueueLength = 0;
                    break;
                case ServiceChannelType.Loss:
                    MaxQueueLength = 1;
                    break;
                default:
                    MaxQueueLength = 10;
                    break;
            }
        }

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #region IBlock

        public void Initialize()
        {
            _innerBuffer.Clear();
            MoveToFreeState();
            Storage = new ServiceChannelStorage();
            Statistic = new ServiceChannelStatistic(this);
        }

        #endregion

        #region ISenderBlock

        public void ConnectReceiverBlock(IReceiverBlock block)
        {
            _receiverBlock = block;
        }

        public void DisconnectReceiverBlock(IReceiverBlock receiverBlock)
        {
            _receiverBlock = null;
        }

        public void Send(Request request)
        {
            if (_receiverBlock == null)
            {
                CollectStatisticOnRequestExitFromSystem(request);
                return;
            }
            _receiverBlock.Receive(request);
        }

        private void CollectStatisticOnRequestExitFromSystem(Request request)
        {
            StatisticController.GetInstance().Collector.ReceiveServiced(request);
        }

        #endregion

        #region IReceiverBlock

        public void Receive(Request request)
        {
            if (ShouldBeRejected(request))
            {
                RejectRequest(request);
                return;
            }

            UpdateWaitingTime(request);
            if (_innerBuffer.IsWaitingTimeOver(request))
            {
                RejectRequest(request);
                return;
            }

            _innerBuffer.Add(request);
        }

        private bool ShouldBeRejected(Request request)
        {
            return ChannelType == ServiceChannelType.Loss && IsBusy;
        }

        /// <summary>
        /// Обновляет время ожидания заявки или канала обслуживания.
        /// Метод предназначен для того, чтобы учитывать отрезок времени, от того момента, когда заявка пришла в очередь и до начала обслуживания следующей заявки.
        /// Основан на утверждении о том, что в момент прихода заявки в канал обслуживания, общее время жизни заявки должно быть равно общему времени жизни 
        /// канала обслуживания в системе.
        /// </summary>
        /// <param name="request"></param>
        private void UpdateWaitingTime(Request request)
        {
            var requestLiveTime = request.InitTime + request.CommonServiceTime + request.CommonWaitingTime;
            var scLiveTime = Storage.WorkingTime + Storage.WaitingTime;

            Storage.WaitingTime += Math.Max(0, requestLiveTime - scLiveTime);
            request.LastQueueWaitingTime = Math.Max(0, scLiveTime - requestLiveTime);
            request.CommonWaitingTime += request.LastQueueWaitingTime;
        }

        #endregion

        public void Service()
        {
            if (_innerBuffer.IsEmpty())
            {
                MoveToFreeState();
                return;
            }

            if (!IsBusy) MoveToBusyState();

            _servedRequest = _innerBuffer.Get();

            UpdateCommonRequestsWaitingTimeInQueue();
            ServiceRequest();
            Send(_servedRequest);

            RaiseActionExecuted();
        }

        private void UpdateCommonRequestsWaitingTimeInQueue()
        {
            Storage.CommonRequestsWaitingTimeInQueue += _servedRequest.LastQueueWaitingTime;
        }

        private void ServiceRequest()
        {
            var servicedTime = ServiceGenerator.GetNext();
            UpdateServedRequestTime(servicedTime);
            UpdateServiceChannelTime(servicedTime);
            UpdateQueue(servicedTime);
            IncCountOfServiced();
        }

        private void MoveToBusyState()
        {
            _innerBuffer.OnRequestAdded -= Service;
            IsBusy = true;
            //Storage.AddStateChangingTime(Storage.LifeTime);
            StatisticController.GetInstance().Statistic.UpdateServiceChannelsWorkingDistribution(Id, Storage.LifeTime, IsBusy);
        }

        private void MoveToFreeState()
        {
            _innerBuffer.OnRequestAdded += Service;
            IsBusy = false;
            //Storage.AddStateChangingTime(Storage.LifeTime);
            StatisticController.GetInstance().Statistic.UpdateServiceChannelsWorkingDistribution(Id, Storage.LifeTime, IsBusy);
        }

        private void RaiseActionExecuted()
        {
            if (OnActionExecuted != null)
            {
                OnActionExecuted(new PriorityAction(Service, Storage.WorkingTime + Storage.WaitingTime, false));
            }
        }

        private void IncCountOfServiced()
        {
            Storage.CountOfServiced++;
        }

        private void UpdateServiceChannelTime(double servicedTime)
        {
            Storage.WorkingTime += servicedTime;
            Storage.WaitingTime = _servedRequest.InitTime + _servedRequest.CommonServiceTime +
                                  _servedRequest.CommonWaitingTime - Storage.WorkingTime;
        }

        private void UpdateQueue(double servicedTime)
        {
            _innerBuffer.UpdateRequestsInQueue(servicedTime);
        }

        private void UpdateServedRequestTime(double servicedTime)
        {
            _servedRequest.CommonServiceTime += servicedTime;
        }

        private void RejectRequest(Request request)
        {
            StatisticController.GetInstance().Collector.ReceiveRejected(request);
            Storage.CountOfRejected++;
        }

        #endregion
    }
}